Chapter 7. EJB 2.0 CMP: Entity RelationshipsIn Chapter 6, you learned about basic EJB 2.0 container-managed persistence. This material included coverage of container-managed persistence fields and an introduction to a basic container-managed relationship field. In this chapter, we will continue to develop the Customer EJB and discuss in detail each of the seven possible relationships that entity beans can have with each other. For entity beans to model real-world business concepts, they must be capable of forming complex relationships with each other. This was difficult to accomplish in EJB 1.1 container-managed persistence because of the simplicity of the programming model. In EJB 1.1, entity beans could have persistence fields but not relationship fields. In EJB 2.0, relationship fields can model complex relationships between entity beans. In Chapter 6, we demonstrated a one-to-one relationship between the Customer and Address EJBs. This relationship was unidirectional; the Customer had a reference to the Address, but the Address did not have a reference back to the Customer. This is a perfectly legitimate relationship, but other relationships are also possible. For example, each Address could also reference its Customer. This is an example of a bidirectional one-to-one relationship, in which both participants maintain references to one another. In addition to one-to-one relationships, entity beans can have one-to-many, many-to-one, and many-to-many relationships. For example, the Customer EJB can have many phone numbers, but each phone number belongs to only one Customer (a one-to-many relationship). A Customer may also have been on many Cruises in the past, and each Cruise will have had many Customers (a many-to-many relationship). 7.1 The Seven Relationship TypesSeven types of relationships can exist between EJBs. This chapter examines those relationships and how the beans' code and deployment descriptors work together to define the relationships. First, let's look at the different types of relationships that are possible. There are four types of cardinality: one-to-one, one-to-many, many-to-one, and many-to-many. On top of that, each relationship can be either unidirectional or bidirectional. That yields eight possibilities, but if you think about it, you'll realize that one-to-many and many-to-one bidirectional relationships are actually the same thing. Thus, there are only seven distinct relationship types. To understand the relationships, it helps to think about some simple examples. We'll expand on the following examples in the course of the chapter:
7.1.1 Abstract Persistence SchemaIn Chapter 6, you learned how to form a basic relationship between the Customer and Address entity beans using the abstract programming model. In reality, the abstract programming model is only half the equation. In addition to declaring abstract accessor methods, a bean developer must describe the cardinality and direction of the entity-to-entity relationships in the bean's deployment descriptor. This is handled in the <relationships> section of the XML deployment descriptor. As we discuss each type of relationship in the following sections, we will examine both the abstract programming model and the XML elements. The purpose of this section is to introduce you to the basic elements used in the XML deployment descriptor, to better prepare you for subsequent sections on specific relationship types. In this book we always refer to the Java programming idioms used to describe relationships—specifically, the abstract accessor methods—as the abstract programming model. When referring to the XML deployment descriptor elements, we use the term abstract persistence schema . In the EJB 2.0 specification the term abstract persistence schema actually refers to both the Java idioms and the XML elements, but this book separate these concepts so we can discuss them more easily. An entity bean's abstract persistence schema is defined in the <relationships> section of the XML deployment descriptor for that bean. The <relationships> section falls between the <enterprise-beans> section and the <assembly-descriptor> section. Within the <relationships> element, each entity-to-entity relationship is defined in a separate <ejb-relation> element: <ejb-jar> <enterprise-beans> ... </enterprise-beans> <relationships> <ejb-relation> ... </ejb-relation> <ejb-relation> ... </ejb-relation> </relationships> <assembly-descriptor> ... </assembly-descriptor> </ejb-jar> Defining relationship fields requires that an <ejb-relation> element be added to the XML deployment descriptor for each entity-to-entity relationship. These <ejb-relation> elements complement the abstract programming model. For each pair of abstract accessor methods that define a relationship field, there is an <ejb-relation> element in the deployment descriptor. EJB 2.0 requires that the entity beans that participate in a relationship be defined in the same XML deployment descriptor. Here is a partial listing of the deployment descriptor for the Customer and Address EJBs, with the emphasis on the elements that define the relationship: <ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CustomerEJB</ejb-name> <local-home>com.titan.customer.CusomterHomeLocal</local-home> <local>com.titan.customer.CustomerLocal</local> ... </entity> <entity> <ejb-name>AddressEJB</ejb-name> <local-home>com.titan.address.AddressHomeLocal</local-home> <local>com.titan.address.AddressLocal</local> ... </entity> ... </enterprise-beans> <relationships> <ejb-relation> <ejb-relation-name>Customer-Address</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-an-Address </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>homeAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Address-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>AddressEJB</ejb-name> </relationship-role-source> </ejb-relationship-role> </ejb-relation> </relationships> </ejb-jar> All relationships between the Customer EJB and other entity beans, such as CreditCard, Address, and Phone, require that we define an <ejb-relation> element to complement the abstract accessor methods. Every relationship may have a relationship name, which is declared in the <ejb-relation-name> element. This serves to identify the relationship for individuals reading the deployment descriptor or for deployment tools, but it's not required. Every <ejb-relation> element has exactly two <ejb-relationship-role> elements, one for each participant in the relationship. In the previous example, the first <ejb-relationship-role> declares the Customer EJB's role in the relationship. We know this because the <relationship-role-source> element specifies the <ejb-name> as CustomerEJB. CustomerEJB is the <ejb-name> used in the Customer EJB's original declaration in the <enterprise-beans> section. The <relationship-role-source> element's <ejb-name> must always match an <ejb-name> element in the <enterprise-beans> section. The <ejb-relationship-role> element also declares the cardinality, or multiplicity, of the role. The <multiplicity> element can either be One or Many. In this case, the Customer EJB's <multiplicity> element has a value of One, which means that every Address EJB has a relationship with exactly one Customer EJB. The Address EJB's <multiplicity> element also specifies One, which means that every Customer EJB has a relationship with exactly one Address EJB. If the Customer EJB had a relationship with many Address EJBs, the Address EBJ's <multiplicity> element would be set to Many. In Chapter 6, we defined the Customer EJB as having abstract accessor methods for getting and setting the Address EJB in the homeAddress field, but the Address EJB did not have abstract accessor methods for the Customer EJB. In this case, the Customer EJB maintains a reference to the Address EJB, but the Address EJB doesn't maintain a reference back to the Customer EJB. This is a unidirectional relationship, which means that only one of the entity beans in the relationship maintains a container-managed relationship field. If the bean described by the <ejb-relationship-role> element maintains a reference to the other bean in the relationship, that reference must be declared as a container-managed relationship field in the <cmr-field> element. The <cmr-field> element is declared under the <ejb-relationship-role> element: <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-an-Address </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>homeAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> EJB 2.0 requires that the <cmr-field-name> begin with a lowercase letter. For every relationship field defined by a <cmr-field> element, there must be a pair of matching abstract accessor methods in the bean class. One method in this pair must be defined with the method name set<cmr-field-name>(), with the first letter of the <cmr-field-name> value changed to uppercase. The other method is defined as get<cmr-field-name>(), also with the first letter of the <cmr-field-name> value in uppercase. In the previous example, the <cmr-field-name> is homeAddress, which corresponds to the getHomeAddress() and setHomeAddress() abstract accessor methods defined in the CustomerBean class: // bean class code public abstract void setHomeAddress(AddressLocal address); public abstract AddressLocal getHomeAddress(); // XML deployment descriptor declaration <cmr-field> <cmr-field-name>homeAddress</cmr-field-name> </cmr-field> The return type of the get<cmr-field-name>() method and the parameter type of the set<cmr-field-name>() must be the same. The type must be the local interface of the entity bean that is referenced or one of two java.util.Collection types. In the case of the homeAddress relationship field, we are using the Address EJB's local interface, AddressLocal. Collection types are discussed in more detail in one-to-many, many-to-one, and many-to-many relationships later in this chapter. Now that we have established a basic understanding of how elements are declared in the abstract persistence schema, we are ready to discuss each of the seven types of relationships in more detail. In the process, we will introduce additional entity beans that have relationships with the Customer EJB, including the CreditCard, Phone, Ship, and Reservation EJBs. It's important to understand that although entity beans may have both local and remote interfaces, a container-managed relationship field can use only the entity bean's local interface when persisting a relationship. So, for example, it is illegal to define an abstract accessor method that has an argument type of javax.ejb.EJBObject (a remote interface type). All container-managed relationships are based on javax.ejb.EJBLocalObject (local interface) types. 7.1.2 Database ModelingThis chapter discusses several different database table schemas. These schemas are intended only to demonstrate possible relationships between entities in the database; they are not prescriptive. For example, the Address-Customer relationship is manifested by having the CUSTOMER table maintain a foreign key to the ADDRESS table. This is not how most databases will be organized—instead, they will probably use a link table or have the ADDRESS table maintain a foreign key to the CUSTOMER. However, this schema shows how EJB 2.0's container-managed persistence can support different database organizations. Throughout this chapter, we assume that the database tables are created before the EJB application—in other words, that the EJB application is mapped to a legacy database. Some vendors offer tools that generate tables automatically according to the relationships defined between the entity beans. These tools may create schemas that are very different from the ones explored here. In other cases, vendors that support established database schemas may not have the flexibility to support the schemas illustrated in this chapter. As an EJB developer, you must be flexible enough to adapt to the facilities provided by your EJB vendor. 7.1.3 One-to-One Unidirectional RelationshipAn example of a one-to-one unidirectional relationship is the relationship between the Customer EJB and the Address EJB defined in Chapter 6. In this case, each Customer has exactly one Address and each Address has exactly one Customer. Which bean references which determines the direction of navigation. While the Customer has a reference to the Address, the Address doesn't reference the Customer. This is a unidirectional relationship because you can go only from the Customer to the Address, and not the other way around. In other words, an Address EJB has no idea who owns it. Figure 7-1 shows this relationship. Figure 7-1. One-to-one unidirectional relationship
7.1.3.1 Relational database schemaAs shown in Figure 7-2, one-to-one unidirectional relationships normally use a fairly typical relational database schema in which one table contains a foreign key (pointer) to another table. In this case, the CUSTOMER table contains a foreign key to the ADDRESS table, but the ADDRESS table doesn't contain a foreign key to the CUSTOMER table. This allows records in the ADDRESS table to be shared by other tables, a scenario explored in Section 7.1.10. The fact that the database schema is not the same as the abstract persistence schema illustrates that they are somewhat independent. Figure 7-2. One-to-one unidirectional relationship in RDBMS
7.1.3.2 Abstract programming modelAs you learned in Chapter 6, abstract accessor methods are used to define relationship fields in the bean class. When an entity bean maintains a reference to another bean, it defines a pair of abstract accessor methods to model that reference. In unidirectional relationships, which can be navigated only one way, only one of the enterprise beans defines these abstract accessor methods. Thus, inside the CustomerBean class you can call the getHomeAddress()/setHomeAddress() methods to access the Address EJBs, but there are no methods inside the AddressBean class to access the Customer EJB. The Address EJB can be shared between relationship fields of the same enterprise bean, but it cannot be shared between Customer EJBs. If, for example, the Customer EJB defines two relationship fields, billingAddress and homeAddress, as one-to-one unidirectional relationships with the Address EJB, these two fields can reference the same Address EJB: public class CustomerBean implements javax.ejb.EntityBean { ... public void setAddress(String street,String city,String state,String zip) { ... address = addressHome.createAddress(street, city, state, zip); this.setHomeAddress(address); this.setBillingAddress(address); AddressLocal billAddr = this.getBillingAddress(); AddressLocal homeAddr = this.getHomeAddress(); if(billAddr.isIdentical(homeAddr)) // always true ... } ... } If at any time you want to make the billingAddress different from the homeAddress, you can simply set it equal to a different Address EJB. Sharing a reference to another bean between two relationship fields in the same entity is sometimes very convenient, though. In order to support this type of relationship, a new billing address field might be added to the CUSTOMER table: CREATE TABLE CUSTOMER ( ID INT PRIMARY KEY, LAST_NAME CHAR(20), FIRST_NAME CHAR(20), ADDRESS_ID INT, BILLING_ADDRESS_ID INT } As the earlier example shows, it is possible for two fields in a bean (in this case, the homeAddress and billingAddress fields in the Customer EJB) to reference the same relationship (i.e., a single Address EJB) if the relationship type is the same. However, it is not possible to share a single Address EJB between two different Customer EJBs. If, for example, the home Address of Customer A were assigned as the home Address of Customer B, the Address would be moved, not shared, so that Customer A wouldn't have a home Address any longer. As you can see in Figure 7-3, Address 2 is initially assigned to Customer B, but becomes disconnected when Address 1 is reassigned to Customer B. Figure 7-3. Exchanging references in a one-to-one unidirectional relationship
This seemingly strange side effect is simply a natural result of how the relationship is defined. The Customer-to-Address EJB relationship was defined as one-to-one, so the Address EJB can be referenced by only one Customer EJB. If the Customer EJB does not have an Address EJB associated with its AddressHome field, the getHomeAddress() method will return null. This is true of all container-managed relationship fields that reference a single entity bean. 7.1.3.3 Abstract persistence schemaWe defined the XML elements for the Customer-Address relationship earlier in this chapter, so we won't go over them again. The <ejb-relation> element used in that section declared a one-to-one unidirectional relationship. If, however, the Customer EJB did maintain two relationship fields with the Address EJB—homeAddress and billingAddress—each of these relationships would have to be described in its own <ejb-relation> element: <relationships> <ejb-relation> <ejb-relation-name>Customer-HomeAddress</ejb-relation-name> <ejb-relationship-role> ... <cmr-field> <cmr-field-name>homeAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> ... </ejb-relationship-role> </ejb-relation> <ejb-relation> <ejb-relation-name>Customer-BillingAddress</ejb-relation-name> <ejb-relationship-role> ... <cmr-field> <cmr-field-name>billingAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> ... </ejb-relationship-role> </ejb-relation> </relationships> 7.1.4 One-to-One Bidirectional RelationshipWe can expand our Customer EJB to include a reference to a CreditCard EJB, which maintains credit card information. The Customer EJB will maintain a reference to its CreditCard EJB and the CreditCard EJB will maintain a reference back to the Customer—this makes good sense, since a CreditCard should be aware of who owns it. Since each CreditCard has a reference back to one Customer and each Customer references one CreditCard, we have a one-to-one bidirectional relationship. 7.1.4.1 Relational database schemaThe CreditCard EJB has a corresponding CREDIT_CARD table, so we need to add a CREDIT_CARD foreign key to the CUSTOMER table: CREATE TABLE CREDIT_CARD ( ID INT PRIMARY KEY NOT NULL, EXP_DATE DATE, NUMBER CHAR(20), NAME CHAR(40), ORGANIZATION CHAR(20), CUSTOMER_ID INT } CREATE TABLE CUSTOMER ( ID INT PRIMARY KEY, LAST_NAME CHAR(20), FIRST_NAME CHAR(20), HOME_ADDRESS_ID INT, ADDRESS_ID INT, CREDIT_CARD_ID INT ) One-to-one bidirectional relationships may model relational database schemas in which the two tables hold foreign keys for one another (specifically, two rows in different tables point to each other). Figure 7-4 illustrates how this schema would be implemented for rows in the CUSTOMER and CREDIT_CARD tables. Figure 7-4. One-to-one bidirectional relationship in RDBMS
It is also possible for a one-to-one bidirectional relationship to be established through a linking table, in which each foreign key column must be unique. This is convenient when you do not want to impose relationships on the original tables. We will use linking tables in one-to-many and many-to-many relationships later in this chapter, but it is important to remember that the database schema used in these examples is purely illustrative. The abstract persistence schema of an entity bean may map to a variety of database schemas; the database schema used in these examples are only one possibility. 7.1.4.2 Abstract programming modelTo model the relationship between the Customer and CreditCard EJBs, we'll need to declare a relationship field named customer in the CreditCardBean class: public abstract class CreditCardBean extends javax.ejb.EntityBean { ... // relationship fields public abstract CustomerLocal getCustomer(); public abstract void setCustomer(CustomerLocal local); // persistence fields public abstract Integer getId(); public abstract void setId(Integer id); public abstract Date getExpirationDate(); public abstract void setExpirationDate(Date date); public abstract String getNumber(); public abstract void setNumber(String number); public abstract String getNameOnCard(); public abstract void setNameOnCard(String name); public abstract String getCreditOrganization(); public abstract void setCreditOrganization(String org); // standard callback methods ... } In this case, we use the Customer EJB's local interface (assume one has been created) because relationship fields require local interface types. All the relationships explored in the rest of this chapter assume local interfaces. Of course, the limitation of using local interfaces instead of remote interfaces is that you don't have location transparency. All the entity beans must be located in the same process or Java Virtual Machine ( JVM). Although relationship fields using remote interfaces are not supported in EJB 2.0, it's likely that support for remote relationship fields will be added in a subsequent version of the specification. We can also add a set of abstract accessor methods in the CustomerBean class for the creditCard relationship field: public class CustomerBean implements javax.ejb.EntityBean { ... public abstract void setCreditCard(CreditCardLocal card); public abstract CreditCardLocal getCreditCard(); ... } Although a setCustomer() method is available in the CreditCardBean, we do not have to set the Customer reference on the CreditCard EJB explicitly. When a CreditCard EJB reference is passed into the setCreditCard() method on the CustomerBean class, the EJB container will automatically establish the customer relationship on the Address EJB to point back to the Customer EJB: public class CustomerBean implements javax.ejb.EntityBean { ... public void setCreditCard(Date exp, String numb, String name, String org) throws CreateException { ... card = creditCardHome.create(exp,numb,name,org); // the CreditCard EJB's customer field will be set automatically this.setCreditCard(card); Customer customer = card.getCustomer(); if(customer.isIdentical(ejbContext.getEJBLocalObject()) // always true ... } ... } The rules for sharing a single bean in a one-to-one bidirectional relationship are the same as those for one-to-one unidirectional relationships. While the CreditCard EJB may be shared between relationship fields of the same Customer EJB, it can't be shared between different Customer EJBs. As Figure 7-5 shows, assigning Customer A's CreditCard to Customer B disassociates that CreditCard from Customer A and moves it to Customer B. Figure 7-5. Exchanging references in a one-to-one bidirectional relationship
7.1.4.3 Abstract persistence schemaThe <ejb-relation> element that defines the Customer-to-CreditCard relationship is similar to the one used for the Customer-to-Address relationship, with one important difference—both <ejb-relationship-role> elements have a <cmr-field>: <relationships> <ejb-relation> <ejb-relation-name>Customer-CreditCard</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-a-CreditCard </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>creditCard</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> CreditCard-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CreditCardEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>customer</cmr-field-name> </cmr-field> </ejb-relationship-role> </ejb-relation> </relationships> The fact that both participants in the relationship define <cmr-field> elements (relationship fields) tells us that the relationship is bidirectional. 7.1.5 One-to-Many Unidirectional RelationshipEntity beans can also maintain relationships with multiplicity. This means that one entity bean can aggregate or contain many other entity beans. For example, the Customer EJB may have relationships with many Phone EJBs, each of which represents a phone number. This is very different from the simple one-to-one relationships. One-to-many and many-to-many relationships require the developer to work with a collection of references when accessing the relationship field, instead of a single reference. 7.1.5.1 Relational database schemaTo illustrate a one-to-many unidirectional relationship, we will use a new entity bean, the Phone EJB, for which we must define a table, the PHONE table: CREATE TABLE PHONE ( ID INT PRIMARY KEY NOT NULL, NUMBER CHAR(20), TYPE INT, CUSTOMER_ID INT } One-to-many unidirectional relationships between the CUSTOMER and PHONE tables could be manifested in a relational database in a variety of ways. For this example, we chose to have the PHONE table include a foreign key to the CUSTOMER table. The table of aggregated data can maintain a column of nonunique foreign keys to the aggregating table. In the case of the Customer and Phone EJBs, the PHONE table maintains a foreign key to the CUSTOMER table, and one or more PHONE records may contain foreign keys to the same CUSTOMER record. In other words, in the database the PHONE records point to the CUSTOMER records. In the abstract programming model, however, it is the Customer EJB that points to the Phone EJBs—two schemas are reversed. How does this work? The container system hides the reverse pointer so that it appears as if the Customer is aware of the Phone EJB and not the other way around. When you ask the container to return a Collection of Phone EJBs (invoking the getPhoneNumbers() method), it will query the PHONE table for all the records with a foreign key matching the Customer EJB's primary key. The use of reverse pointers in this type of relationship is illustrated in Figure 7-6. Figure 7-6. One-to-many unidirectional relationship in RDBMS using reverse pointers
This database schema illustrates that the structure and relationships of the actual database can differ from the relationships as defined in the abstract programming model. In this case the tables are set up in reverse, but the EJB container system will manage the beans to meet the specification of the bean developer. This isn't always possible; sometimes the database schema is incompatible with a desired relationship field. however, when you are dealing with legacy databases (i.e., databases that were established before the EJB application) reverse-pointer scenarios like the one illustrated here are common, so supporting this kind of relationship mapping is important. A simpler implementation of the Customer-Phone relationship could use a link table that maintains two columns with foreign keys pointing to both the CUSTOMER and PHONE records. We could then place a unique constraint on the PHONE foreign key column in the link table to ensure that it contains only unique entries (i.e., that every phone has only one customer), while allowing the CUSTOMER foreign key column to contain duplicates. The advantage of the link table is that it doesn't impose the relationship between the CUSTOMER and PHONE records onto either of the tables. 7.1.5.2 Abstract programming modelIn the abstract programming model, we represent multiplicity by defining a relationship field that can point to many entity beans. To do this, we employ the same abstract accessor methods we used for one-to-one relationships, but this time we set the field type to either java.util.Collection or java.util.Set. The Collection maintains a homogeneous group of local EJB object references, which means it contains many references to one kind of entity bean. The Collection type may contain duplicate references to the same entity bean, while the Set type may not. For example, a Customer EJB may have relationships with several phone numbers (e.g., a home phone, work phone, cell phone, fax, etc.), each represented by a Phone EJB. Instead of having a different relationship field for each of these Phone EJBs, the Customer EJB keeps all the Phone EJBs in a collection-based relationship field, which can be accessed through abstract accessor methods: public abstract class CustomerBean implements javax.ejb.EntityBean { ... // relationship fields public java.util.Collection getPhoneNumbers(); public void setPhoneNumbers(java.util.Collection phones); public AddressLocal getHomeAddress();() public void setHomeAddress(AddressLocal local); ... The Phone EJB, like other entity beans, has a bean class and local interface, as shown in the next listing. Notice that the PhoneBean doesn't provide a relationship field for the Customer EJB. It's a unidirectional relationship; the Customer maintains a relationship with many Phone EJBs, but the Phone EJBs do not maintain a relationship field back to the Customer. Only the Customer EJB is aware of the relationship: // the local interface for the Phone EJB public interface PhoneLocal extends javax.ejb.EJBLocalObject { public String getNumber(); public void setNumber(String number); public byte getType(); public void setType(byte type); } // the bean class for the Phone EJB public class PhoneBean implements javax.ejb.EntityBean { public Integer ejbCreate(String number, byte type) { setNumber(number); setType(type); return null; } public void ejbPostCreate(String number,byte type) { } // persistence fields public abstract Integer getId(); public abstract void setId(Integer id); public abstract String getNumber(); public abstract void setNumber(String number); public abstract byte getType(); public abstract void setType(byte type); // standard callback methods ... } To illustrate how an entity bean uses a collection-based relationship field, we will define a method in the CustomerBean class that allows remote clients to add new phone numbers. The method, addPhoneNumber(), uses the phone number arguments to create a new Phone EJB and then add that Phone EJB to a collection-based relationship field named phoneNumbers: public abstract class CustomerBean implements javax.ejb.EntityBean { // business methods public void addPhoneNumber(String number, String type) { InitialContext jndiEnc = new InitialContext(); PhoneHomeLocal phoneHome = jndiEnc.lookup("PhoneHomeLocal"); PhoneLocal phone = phoneHome.create(number,type); Collection phoneNumbers = this.getPhoneNumbers(); phoneNumbers.add(phone); } ... // relationship fields public java.util.Collection getPhoneNumbers(); public void setPhoneNumbers(java.util.Collection phones); ... Note that we created the Phone EJB first, then added it to the phoneNumbers collection-based relationship. We obtained the phoneNumbers Collection object from the getPhoneNumbers() accessor method, then added the new Phone EJB to the Collection just as we would add any object to a Collection. Adding the Phone EJB to the Collection causes the EJB container to set the foreign key on the new PHONE record so that it points back to the Customer EJB's CUSTOMER record. If we had used a link table, a new link record would have been created. From this point forward, the new Phone EJB will be available from the phoneNumbers collection-based relationship. You can also update or remove references in a collection-based relationship field from the relationship using the relationship field accessor method. For example, the following code defines two methods in the CustomerBean class that allow clients to remove or update phone numbers in the bean's phoneNumbers relationship field: public abstract class CustomerBean implements javax.ejb.EntityBean { // business methods public void removePhoneNumber(byte typeToRemove) { Collection phoneNumbers = this.getPhoneNumbers(); Iterator iterator = phoneNumbers.iterator(); while(iterator.hasNext()) { PhoneLocal phone = (PhoneLocal)iterator.next(); if(phone.getType() = typeToRemove) { iterator.remove(phone); break; } } } public void updatePhoneNumber(String number,byte typeToUpdate) { Collection phoneNumbers = this.getPhoneNumbers(); Iterator iterator = phoneNumbers.iterator(); while(iterator.hasNext()) { PhoneLocal phone = (PhoneLocal)iterator.next(); if(phone.getType() = typeToUpdate) { phone.setNumber(number); break; } } } ... // relationship fields public java.util.Collection getPhoneNumbers(); public void setPhoneNumbers(java.util.Collection phones); In the removePhoneNumber() business method, a Phone EJB with the matching type was found and then removed from the collection-based relationship. The phone number is not deleted from the database, it's just disassociated from the Customer EJB (i.e., it is no longer referenced by a Customer). Figure 7-7 shows what happens when a Phone EJB reference is removed from the collection-based relationship. Figure 7-7. Removing a bean reference from a relationship-field collection
The updatePhoneNumber() method actually modifies an existing Phone EJB, changing its state in the database. The Phone EJB is still referenced by the collection-based relationship, but its data has changed. The removePhoneNumber() and updatePhoneNumber() methods illustrate that a collection-based relationship can be accessed and updated just like any other Collection object. In addition, a java.util.Iterator can be obtained from the Collection object for looping operations. However, you should exercise caution when using an iterator over a collection-based relationship. You must not add elements to or remove elements from the Collection object while you are using its Iterator. The only exception to this rule is that the Iterator.remove() method may be called to remove an entry. Although the Collection.add() and Collection.remove() methods can be used in other circumstances, calling these methods while an iterator is in use will result in a java.util.IllegalStateException exception. If no beans have been added to the phoneNumbers relationship field, the getPhoneNumbers() method will return an empty Collection object. <multiplicity> relationship fields never return null. The Collection object used with the relationship field is implemented by the container system, proprietary to the vendor, and tightly coupled with the inner workings of the container. This allows the EJB container to implement performance enhancements such as lazy loading or optimistic concurrency seamlessly, without exposing those proprietary mechanisms to the bean developer.[1] Application-defined Collection objects may be used with container-manager relationship fields only if the elements are of the proper type. For example, it is legal to create a new Collection object and then add that Collection object to the Customer EJB using the setPhoneNumbers() method: public void addPhoneNumber(String number, String type) { ... PhoneLocal phone = phoneHome.create(number,type); Collection phoneNumbers = java.util.Vector(); phoneNumbers.add(phone); // This is allowed this.setPhoneNumbers(phoneNumbers); } // relationship fields public java.util.Collection getPhoneNumbers(); public void setPhoneNumbers(java.util.Collection phones); We have used the getPhoneNumbers() method extensively but have not yet used the setPhoneNumbers() method. In most cases this method will not be used, because it updates an entire collection of phone numbers. However, it can be useful for exchanging like relationships between entity beans. If two Customer EJBs want to exchange phone numbers, they can do so in a variety of ways. The most important thing to keep in mind is that a Phone EJB, as the subject of a one-to-many unidirectional relationship, may reference only one Customer EJB. It can be copied, so that both Customers have Phone EJBs with similar data, but the Phone EJB itself cannot be shared. Imagine, for example, that Customer A wants to transfer all of its phone numbers to Customer B. It can accomplish this by using Customer B's setPhoneNumbers() method, as shown in the following listing (we assume the Customer EJBs are interacting through their local interfaces): CustomerLocal customerA = ... get Customer A CustomerLocal customerB = ... get Customer B Collection phonesA = customerA.getPhoneNumbers(); customerB.setPhoneNumbers( phonesA ); if( customerA.getPhoneNumbers().isEmpty()) // this will be true if( phonesA.isEmpty()) ) // this will be true As Figure 7-8 illustrates, passing one collection-based relationship to another actually disassociates those relationships from the first bean and associates them with the second. In addition, if the second bean already has a Collection of Phone EJBs in its phoneNumbers relationship field, those beans are bumped out of the relationship and disassociated from the bean. Figure 7-8. Exchanging a relationship collection in a one-to-many unidirectional relationship
The result of this exchange may be counterintuitive, but it is necessary to uphold the multiplicity of the relationship, which says that the Phone EJB may have only one Customer EJB. This explains why Phone EJBs 1, 2, and 3 don't reference both Customers A and B, but it doesn't explain why Phone EJBs 4, 5, and 6 are disassociated from Customer B. Why isn't Customer B associated with all the Phone EJBs? The reason is purely a matter of semantics, since the relational database schema wouldn't technically prevent this from occurring. The act of replacing one Collection with another by calling setPhoneNumbers(Collection collection) implies that Customer B's initial Collection object is no longer referenced. In addition to moving whole collection-based relationships between beans, it is possible to move individual Phone EJBs between Customers.These cannot be shared either. For example, if a Phone EJB aggregated by Customer A is added to the relationship collection of Customer B, that Phone EJB changes so that it's now referenced by Customer B instead of Customer A, as Figure 7-9 illustrates. Figure 7-9. Exchanging a bean in a one-to-many unidirectional relationship
One again, it's the multiplicity of the relationship that prevents Phone 1 from referencing both Customer A and Customer B. 7.1.5.3 Abstract persistence schemaThe abstract persistence schema for one-to-many unidirectional relationships has a few significant differences from the <ejb-relation> elements seen so far: <relationships> <ejb-relation> <ejb-relation-name>Customer-Phones</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-many-Phone-numbers </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>phoneNumbers</cmr-field-name> <cmr-field-type>java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Phone-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>PhoneEJB</ejb-name> </relationship-role-source> </ejb-relationship-role> </ejb-relation> </relationships> In the <ejb-relation> element, the multiplicity for the Customer EJB is declared as One, while the multiplicity for the Phone EJB's <ejb-relationship-role> is Many. This obviously establishes the relationship as one-to-many. The fact that the <ejb-relationship-role> for the Phone EJB doesn't specify a <cmr-field> element indicates that the one-to-many relationship is unidirectional; the Phone EJB doesn't contain a reciprocating reference to the Customer EJB. The most interesting change is the addition of the <cmr-field-type> element in the Customer EJB's <cmr-field> declaration. The <cmr-field-type> must be specified for a bean that has a collection-based relationship field (in this case, the phoneNumbers field maintained by the Customer EJB). The <cmr-field-type> can have one of two values, java.util.Collection or java.util.Set, which are the allowed collection-based relationship types. In a future specification, the allowed types for collection-based relationships may be expanded to include java.util.List and java.util.Map, but these are not yet supported. Please refer to Workbook Exercise 7.1, Entity Relationships in CMP 2.0: Part 1. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks. 7.1.6 The Cruise, Ship, and Reservation EJBsTo make things more interesting, we are going to introduce some more entity beans so that we can model the remaining four relationships: many-to-one unidirectional, one-to-many bidirectional, many-to-many bidirectional, and many-to-many unidirectional. In Titan's reservation system, every customer (a.k.a. passenger) can be booked on one or more cruises. Each booking requires a reservation. A reservation may be for one or more (usually two) passengers. Each cruise requires exactly one ship, but each ship may be used for many cruises throughout the year. Figure 7-10 illustrates these relationships. Figure 7-10. Cruise, Ship, Reservation, Cabin, and Customer class diagram
The relationships investigated in the next four sections will each refer back to the above diagram. 7.1.7 Many-to-One Unidirectional RelationshipMany-to-one unidirectional relationships result when many entity beans reference a single entity bean, but the referenced entity bean is unaware of the relationship. In the Titan Cruise business, for example, the concept of a cruise can be captured by a Cruise EJB. As shown in Figure 7-10, each Cruise has a many-to-one relationship with a Ship. This relationship is unidirectional; the Cruise EJB maintains a relationship with the Ship EJB, but the Ship EJB does not keep track of the Cruises for which it is used. 7.1.7.1 Relational database schemaThe relational database schema for the Cruise-to-Ship relationship is fairly simple; it requires that the CRUISE table maintain a foreign key column for the SHIP table, with each row in the CRUISE table pointing to a row in the SHIP table. The CRUISE and SHIP tables are defined below; Figure 7-11 shows the relationship between these tables in the database. Figure 7-11. Many-to-one unidirectional relationship in RDBMS
An enormous amount of data would be required to adequately describe an ocean liner, but we'll use a simple definition of the SHIP table here: CREATE TABLE SHIP ( ID INT PRIMARY KEY NOT NULL, NAME CHAR(30), TONNAGE DECIMAL (8,2) } The CRUISE table maintains data on each cruise's name, ship, and other information that is not germane to this discussion. (Other tables, such as RESERVATIONS, SCHEDULES, and CREW, would have relationships with the CRUISE table through linking tables.) We'll keep it simple and focus on a definition that is useful for the examples in this book: CREATE TABLE CRUISE ( ID INT PRIMARY KEY NOT NULL, NAME CHAR(30), SHIP_ID INT } 7.1.7.2 Abstract programming modelIn the abstract programming model, the relationship field is of type ShipLocal and is maintained by the Cruise EJB. The abstract accessor methods are similar to those defined in the previous examples: public abstract class CruiseBean implements javax.ejb.EntityBean { public Integer ejbCreate(String name, ShipLocal ship) { setName(name); return null; } public void ejbPostCreate(String name, ShipLocal ship) { setShip(ship); } public abstract Integer getId(); public abstract void setId(Integer id); public abstract void setName(String name); public abstract String getName(); public abstract void setShip(ShipLocal ship); public abstract ShipLocal getShip(); // EJB callback methods ... } Notice that the Cruise EJB requires that a ShipLocal reference be passed as an argument when the Cruise is created; this is perfectly natural, since a cruise cannot exist without a ship. According to the EJB 2.0 specification, relationship fields cannot be modified or set in the ejbCreate() method. They must be modified in the ejbPostCreate(), a constraint that is followed in the CruiseBean class. The reasons relationships are set in ejbPostCreate() and not ejbCreate() are simple: the primary key for the entity bean may not be available until after ejbCreate() executes. The primary key is needed if the mapping for the relationship uses the key as a foreign key, so assignment of relationships is postponed until the ejbPostCreate() method completes and the primary key becomes available. This is also true with autogenerated primary keys, which usually require that the insert be done before a primary key can be generated. In addition, referential integrity may specify non-null foreign keys in referencing tables, so the insert must take place first. In reality, the transaction does not complete until both the ejbCreate() and ejbPostCreate() methods have executed, so the vendors are free to choose the best time for database inserts and linking of relationships. The relationship between the Cruise and Ship EJBs is unidirectional, so the Ship EJB doesn't define any relationship fields, just persistence fields: public abstract class ShipBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer primaryKey,String name,double tonnage) { setId(primaryKey); setName(name); setTonnage(tonnage); return null; } public void ejbPostCreate(Integer primaryKey,String name,double tonnage) { } public abstract void setId(Integer id); public abstract Integer getId(); public abstract void setName(String name); public abstract String getName(); public abstract void setTonnage(double tonnage); public abstract double getTonnage(); // EJB callback methods ... } This should all be fairly mundane for you now. The impact of exchanging Ship references between Cruise EJBs should be equally obvious. As shown previously in Figure 7-10, each Cruise may reference only a single Ship, but each Ship may reference many Cruise EJBs. If you take Ship A, which is referenced by Cruise 1, and pass it to Cruise 4, both Cruise 1 and 4 will reference Ship A, as shown in Figure 7-12. Figure 7-12. Sharing a bean reference in a many-to-one unidirectional relationship
7.1.7.3 Abstract persistence schemaThe abstract persistence schema is simple in a many-to-one unidirectional relationship. It uses everything you have already learned, and shouldn't contain any surprises: <ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CruiseEJB</ejb-name> <local-home>com.titan.cruise.CruiseHomeLocal</local-home> <local>com.titan.cruise.CruiseLocal</local> ... </entity> <entity> <ejb-name>ShipEJB</ejb-name> <local-home>com.titan.ship.ShipHomeLocal</local-home> <local>com.titan.ship.ShipLocal</local> ... </entity> ... </enterprise-beans> <relationships> <ejb-relation> <ejb-relation-name>Cruise-Ship</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Cruise-has-a-Ship </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>CruiseEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>ship</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Ship-has-many-Cruises </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>ShipEJB</ejb-name> </relationship-role-source> </ejb-relationship-role> </ejb-relation> </relationships> The <ejb-relationship-role> of the Cruise EJB defines its multiplicity as Many and declares ship as its relationship field. The <ejb-relationship-role> of the Ship EJB defines its multiplicity as One and contains no <cmr-field> declaration, because it's a unidirectional relationship. 7.1.8 One-to-Many Bidirectional RelationshipOne-to-many and many-to-one bidirectional relationships are the same thing, so they are both covered in this section. A one-to-many bidirectional relationship occurs when one entity bean maintains a collection-based relationship field with another entity bean, and each entity bean referenced in the collection maintains a single reference back to its aggregating bean. For example, in the Titan Cruise system, each Cruise EJB maintains a collection of references to all the passenger reservations made for that Cruise, and each Reservation EJB maintains a single reference to its Cruise. The relationship is a one-to-many bidirectional relationship from the perspective of the Cruise EJB, and a many-to-one bidirectional relationship from the perspective of the Reservation EJB. 7.1.8.1 Relational database schemaThe first table we need is the RESERVATION table, which is defined in the following listing. Notice that the RESERVATION table contains, among other things, a column that serves as a foreign key to the CRUISE table: CREATE TABLE RESERVATION ( ID INT PRIMARY KEY NOT NULL, AMOUNT_PAID DECIMAL (8,2), DATE_RESERVED DATE, CRUISE_ID INT } While the RESERVATION table contains a foreign key to the CRUISE table, the CRUISE table doesn't maintain a foreign key back to the RESERVATION table. The EJB container system can determine the relationship between the Cruise and Reservations EJBs by querying the RESERVATION table, so explicit pointers from the CRUISE table to the RESERVATION table are not required. This illustrates once again the separation between the entity bean's view of its persistence relationships and the database's actual implementation of those relationships. The relationship between the RESERVATION and CRUISE tables is shown in Figure 7-13. Figure 7-13. One-to-many/many-to-one bidirectional relationship in RDBMS
As an alternative, we could have used a link table that would declare foreign keys to both the CRUISE and RESERVATION tables. This link table would probably impose a unique constraint on the RESERVATION foreign key to ensure that each RESERVATION record had only one corresponding CRUISE record. 7.1.8.2 Abstract programming modelTo model the relationship between Cruises and Reservations, we'll first define the Reservation EJB, which maintains a relationship field to the Cruise EJB: public abstract class ReservationBean implements javax.ejb.EntityBean { public Integer ejbCreate(CruiseLocal cruise) { return null; } public void ejbPostCreate(CruiseLocal cruise) { setCruise(cruise); } public abstract void setCruise(CruiseLocal cruise); public abstract CruiseLocal getCruise(); public abstract Integer getId(); public abstract void setId(Integer id); public abstract void setAmountPaid(float amount); public abstract float getAmountPaid(); public abstract void setDate(Date date); public abstract Date getDate(); // EJB callback methods ... } When a Reservation EJB is created, a reference to the Cruise for which it is created must be passed to the create() method. Notice that the CruiseLocal reference is set in the ejbPostCreate() method and not the ejbCreate() method. As stated previously, the ejbCreate() method is not allowed to update relationship fields; that is the job of the ejbPostCreate() method. We need to add a collection-based relationship field to the Cruise EJB so that it can reference all the Reservation EJBs that were created for it: public abstract class CruiseBean implements javax.ejb.EntityBean { ... public abstract void setReservations(Collection res); public abstract Collection getReservations(); public abstract Integer getId(); public abstract void setId(Integer id); public abstract void setName(String name); public abstract String getName(); public abstract void setShip(ShipLocal ship); public abstract ShipLocal getShip(); // EJB callback methods ... } The interdependency between the Cruise and Reservation EJBs produces some interesting results when you create a relationship between these beans. For example, the act of creating a Reservation EJB automatically adds that entity bean to the collection-based relationship of the Cruise EJB: CruiseLocal cruise = ... get CruiseLocal reference ReservationLocal reservation = reservationHomeLocal.create( cruise ); Collection collection = cruise.getReservations(); if(collection.contains(reservation)) // always returns true This is a side effect of the bidirectional relationship. Any Cruise referenced by a specific Reservation has a reciprocal reference back to that Reservation. If Reservation X references Cruise A, Cruise A must have a reference to Reservation X. When you create a new Reservation EJB and set the Cruise reference on that bean, the Reservation is automatically added to the Cruise EJB's reservation field.[2] Sharing references between beans has some of the ugly consequences we learned about earlier. For example, passing a collection of Reservations referenced by Cruise A to Cruise B actually moves those relationships to Cruise B, so Cruise A has no more Reservations (see Figure 7-14). Figure 7-14. Sharing an entire collection in a one-to-many bidirectional relationship
As was the case with the Customer and Phone EJBs (see Figure 7-8), this effect is usually undesirable and should be avoided, as it displaces the set of Reservation EJBs formerly associated with Cruise B. You can move an entire collection from one bean to another and combine it with the second bean's collection by using the Collection.addAll() method, as shown in Figure 7-15.[3] If you move Cruise A's collection of references to Cruise B, Cruise A will no longer reference any Reservation EJBs, while Cruise B will reference those it referenced before the exchange as well as those it acquired from Cruise A. Figure 7-15. Using Collection.addAll( ) in a one-to-many bidirectional relationship
The impact of moving an individual Reservation EJB from one Cruise to another is similar to what we have seen with other one-to-many relationships: the result is the same as was shown in Figure 7-9, when a Phone was moved from one Customer to another. It's interesting to note that the net effect of using Collection.addAll() in this scenario is the same as using Collection.add() on the target collection for every element in the source collection. In both cases, you move every element from the source collection to the target collection. Once again, container-managed relationship fields, collection-based or otherwise, must always use the javax.ejb.EJBLocalObject (local) interface of a bean and never the javax.ejb.EJBObject (remote) interface. It would be illegal, for example, to try to add the remote interface of the Reservation EJB (if it has one) to the Cruise EJB's Reservation Collection. Any attempt to add a remote interface type to a collection-based relationship field will result in a java.lang.IllegalArgumentException. 7.1.8.3 Abstract persistence schemaThe abstract persistence schema for the Cruise-Reservation relationship doesn't introduce any new concepts. The Cruise and Reservation <ejb-relationship-role> elements both have <cmr-field> elements. The Cruise specifies One as its multiplicity, while Reservation specifies Many. Here's the code: <ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CruiseEJB</ejb-name> <local-home>com.titan.cruise.CruiseHomeLocal</local-home> <local>com.titan.cruise.CruiseLocal</local> ... </entity> <entity> <ejb-name>ReservationEJB</ejb-name> <local-home> com.titan.reservations.ReservationHomeLocal </local-home> <local>com.titan.reservation.ReservationLocal</local> ... </entity> ... </enterprise-beans> <relationships> <ejb-relation> <ejb-relation-name>Cruise-Reservation </ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Cruise-has-many-Reservations </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CruiseEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>reservations</cmr-field-name> <cmr-field-type> java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Reservation-has-a-Cruise </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>ReservationEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>cruise</cmr-field-name> </cmr-field> </ejb-relationship-role> </ejb-relation> </relationships> 7.1.9 Many-to-Many Bidirectional RelationshipMany-to-many bidirectional relationships occur when many beans maintain a collection-based relationship field with another bean, and each bean referenced in the Collection maintains a collection-based relationship field back to the aggregating beans. For example, in Titan Cruises every Reservation EJB may reference many Customers (a family can make a single reservation) and each Customer can have many reservations (a person may make more than one reservation in a year). This is an example of a many-to-many bidirectional relationship; the customer keeps track of all of its reservations, and each reservation may be for many customers. 7.1.9.1 Relational database schemaThe RESERVATION and CUSTOMER tables have already been established. In order to establish a many-to-many bidirectional relationship, the RESERVATION_CUSTOMER_LINK table is created. This table maintains two foreign key columns: one for the RESERVATION table and another for the CUSTOMER table: CREATE TABLE RESERVATION_CUSTOMER_LINK ( RESERVATION_ID INT, CUSTOMER_ID INT } The relationship between the CUSTOMER, RESERVATION, and CUSTOMER_RESERVATION_LINK tables is illustrated in Figure 7-16. Figure 7-16. Many-to-many bidirectional relationship in RDBMS
Many-to-many bidirectional relationships always require a link in a normalized relational database. 7.1.9.2 Abstract programming modelTo model the many-to-many bidirectional relationship between the Customer and Reservation EJBs, we need to modify both bean classes to include collection-based relationship fields: public abstract class ReservationBean implements javax.ejb.EntityBean { public Integer ejbCreate(CruiseLocal cruise,Collection customers) { return null; } public void ejbPostCreate(CruiseLocal cruise,Collection customers) { setCruise(cruise); Collection myCustomers = this.getCustomers(); myCustomers.addAll(customers); } public abstract void setCustomers(Set customers); public abstract Set getCustomers(); ... } The abstract accessor methods defined for the customers relationship field declare the Collection type as java.util.Set. The Set type should contain only unique Customer EJBs and no duplicates. Duplicate Customers would introduce some interesting but undesirable side effects in Titan's reservation system. To maintain a valid passenger count, and to avoid overcharging customers, Titan requires that a Customer be booked only once in the same Reservation. The Set collection type expresses this restriction. The effectiveness of the Set collection type depends largely on referential-integrity constraints established in the underlying database. In addition to adding the getCustomers()/setCustomers() abstract accessors, we have modified the ejbCreate()/ejbPostCreate() methods to take a Collection of Customer EJBs. When a Reservation EJB is created, it must be provided with a list of Customer EJBs that it will add to its own Customer EJB collection. As is always the case, container-managed relationship fields cannot be modified in the ejbCreate() method. It's the ejbPostCreate() method's job to modify container-managed relationships fields when a bean is created. We have also modified the Customer EJB to allow it to maintain a collection-based relationship with all of its Reservations. While the idea of a Customer having multiple Reservations may seem odd, it's possible for someone to book more than one cruise in advance. To allow for this possibility, we have enhanced the Customer EJB to include a reservations relationship field: public abstract class CustomerBean implements javax.ejb.EntityBean { ... // relationship fields public abstract void setReservations(Collection reservations); public abstract Collection getReservations(); ... When a Reservation EJB is created, it is passed references to its Cruise and to a collection of Customers. Because the relationship is defined as bidirectional, the EJB container will automatically add the Reservation EJB to the reservations relationship field of the Customer EJB. The following code fragment illustrates this: Collection customers = ... get local Customer EJBs CruiseLocal cruise = ... get a local Cruise EJB ReservationHomeLocal = ... get local Reservation home ReservationLocal myReservation = resHome.create(cruise, customers); Iterator iterator = customers.iterator(); while(iterator.hasNext()) { CustomerLocal customer = (CustomerLocal)iterator.next(); Collection reservations = customer.getReservations(); if( reservations.contains( myReservation )) // this will always be true } Exchanging bean references in many-to-many bidirectional relationships results in true sharing, where each relationship maintains a reference to the transferred collection. This type of relationship is illustrated in Figure 7-17. Figure 7-17. Using Collection.addAll( ) in a many-to-many bidirectional relationship
Of course, using the setCustomers() or setReservations() method will end up changing the references between the entity bean and the elements in the original collection, but the other relationships held by those elements are unaffected. Figure 7-18 illustrates what happens when an entire collection is shared in a many-to-many bidirectional relationship. Figure 7-18. Sharing an entire collection in a many-to-many bidirectional relationship
After the setCustomers() method is invoked on Reservation D, Reservation D's Customers change to Customers 1, 2, and 3. Customers 1, 2, and 3 were also referenced by Reservation A before the sharing operation and remain referenced by Reservation A after it's complete. In fact, only the relationships between Reservation D and Customers 4, 5, and 6 are impacted. The relationship between Customers 4, 5, and 6 and other Reservation EJBs are not affected by the sharing operation. This is a unique property of many-to-many relationships (both bidirectional and unidirectional); operations on the relationship fields affect only those specific relationships, they do not impact either party's relationships with other beans of the same relationship type. 7.1.9.3 Abstract persistence schemaThe abstract persistence schema of a many-to-many bidirectional relationship introduces nothing new and should contain no surprises. Each <ejb-relationship-role> specifies Many as its multiplicity and declares a <cmr-field> of a specific Collection type: <ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CustomerEJB</ejb-name> <local-home>com.titan.customer.CustomerHomeLocal</local-home> <local>com.titan.customer.CustomerLocal</local> ... </entity> <entity> <ejb-name>ReservationEJB</ejb-name> <local-home> com.titan.reservation.ReservationHomeLocal</local-home> <local>com.titan.reservation.ReservationLocal</local> ... </entity> ... </enterprise-beans> <relationships> <ejb-relation> <ejb-relation-name>Customer-Reservation</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-many-Reservations </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>reservations</cmr-field-name> <cmr-field-type>java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Reservation-has-many-Customers </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>ReservationEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>customers</cmr-field-name> <cmr-field-type>java.util.Set</cmr-field-type> </cmr-field> </ejb-relationship-role> </ejb-relation> </relationships> 7.1.10 Many-to-Many Unidirectional RelationshipMany-to-many unidirectional relationships occur when many beans maintain a collection-based relationship with another bean, but the bean referenced in the Collection does not maintain a collection-based relationship back to the aggregating beans. In Titan's reservation system, every Reservation is assigned a Cabin on the Ship. This allows a Customer to reserve a specific Cabin (e.g., a deluxe suite or a cabin with sentimental significance) on the Ship. In this case, each Reservation may be for more than one Cabin, since each Reservation can be for more than one Customer. An example is a family that makes a Reservation for five people for two adjacent Cabins (one for the kids and the other for the parents). While the Reservation must keep track of the Cabins it reserves, it's not necessary for the Cabins to track all the Reservations made by all the Cruises. The Reservation EJBs reference a collection of Cabin beans, but the Cabin beans do not maintain references back to the Reservations. 7.1.10.1 Relational database schemaOur first order of business is to declare a CABIN table: CREATE TABLE CABIN ( ID INT PRIMARY KEY NOT NULL, SHIP_ID INT, NAME CHAR(10), DECK_LEVEL INT, BED_COUNT INT } Notice that the CABIN table maintains a foreign key to the SHIP table. While this relationship is important, we won't explore it here because we've already covered the one-to-many bidirectional relationship in this chapter. The Cabin-Ship relationship is included in Figure 7-10, however, for completeness. To accommodate the many-to-many unidirectional relationship between the RESERVATION and CABIN table, we will need a RESERVATION_CABIN_LINK table: CREATE TABLE RESERVATION_CABIN_LINK ( RESERVATION_ID INT, CABIN_ID INT } The relationship between the CABIN records and the RESERVATION records through the RESERVATION_CABIN_LINK table is illustrated in Figure 7-19. Figure 7-19. Many-to-many unidirectional relationship in RDBMS
7.1.10.2 Abstract programming modelTo model this relationship, we need to add a collection-based relationship field for Cabin beans to the Reservation EJB: public abstract class ReservationBean implements javax.ejb.EntityBean { ... public abstract void setCabins(Set cabins); public abstract Set getCabins(); ... } In addition, we need to define a Cabin bean. Notice that the Cabin bean doesn't maintain a relationship back to the Reservation EJB. The lack of a container-managed relationship field for the Reservation EJB tells us the relationship is unidirectional: public abstract class CabinBean implements javax.ejb.EntityBean { public Integer ejbCreate(ShipLocal ship, String name) { this.setName(name); return null; } public void ejbPostCreate(ShipLocal ship, String name) { this.setShip(ship); } public abstract void setShip(ShipLocal ship); public abstract ShipLocal getShip(); public abstract Integer getId(); public abstract void setId(Integer id); public abstract void setName(String name); public abstract String getName(); public abstract void setBedCount(int count); public abstract int getBedCount(); public abstract void setDeckLevel(int level); public abstract int getDeckLevel(); // EJB callback methods } Although the Cabin bean doesn't define a relationship field for the Reservation EJB, it does define a one-to-many bidirectional relationship for the Ship EJB. The effect of exchanging relationship fields in a many-to-many unidirectional relationship is basically the same as that in a many-to-many bidirectional relationship. Use of the Collection.addAll() operation to share entire collections has the same net effect we noted in the previous section on many-to-many bidirectional relationships. The only difference is that the arrows point only one way, instead of both ways. If a Reservation removes a Cabin bean from its collection-based relationship field, the operation doesn't affect other Reservation EJBs that reference that Cabin bean. This is illustrated in Figure 7-20. Figure 7-20. Removing beans in a many-to-many unidirectional relationship
7.1.10.3 Abstract persistence schemaThe abstract persistence schema for the Reservation-Cabin relationship holds no surprises. The multiplicity of both <ejb-relationship-role> elements is Many, but only the Reservation EJB's <ejb-relationship-role> defines a <cmr-field>: <ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CabinEJB</ejb-name> <local-home>com.titan.cabin.CabinHomeLocal</local-home> <local>com.titan.cabin.CabinLocal</local> ... </entity> <entity> <ejb-name>ReservationEJB</ejb-name> <local-home> com.titan.reservation.ReservationHomeLocal</local-home> <local>com.titan.reservation.ReservationLocal</local> ... </entity> ... </enterprise-beans> <relationships> <ejb-relation> <ejb-relation-name>Cabin-Reservation</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Cabin-has-many-Reservations </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>CabinEJB</ejb-name> </relationship-role-source> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Reservation-has-many-Customers </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>ReservationEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>cabins</cmr-field-name> <cmr-field-type>java.util.Set</cmr-field-type> </cmr-field> </ejb-relationship-role> </ejb-relation> </relationships> Please refer to Workbook Exercise 7.2, Entity Relationships in CMP 2.0: Part 2. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks. 7.1.11 Co-location and the Deployment DescriptorOnly entity beans that are deployed together with the same deployment descriptor can have relationships with each other. When deployed together, the entity beans are seen as a single deployment unit or application, in which all the entities are using the same database and are co-located in the same JVM. This restriction makes it possible for the EJB container system to use lazy loading, optimistic concurrency, and other performance optimizations. While it would technically be possible to support relationships across deployments or even across container systems, the difficulty of doing so, combined with the expected degradation in performance, was reason enough to limit the relationship fields to those entity beans that are deployed together. In the future, entity relationships may be expanded to include remote references to entities deployed in other containers or other JAR files in the same container, but remote references are not allowed as relationship types in EJB 2.0. 7.1.12 Cascade Delete and RemoveAs you learned in Chapter 5, invoking the remove() operation on the EJB home or EJB object of an entity bean deletes that entity bean's data from the database. Deleting the bean's data, of course, has an impact on the relationships that entity bean has with other entity beans. When an entity bean is deleted, the EJB container first removes it from any relationships it maintains with other entity beans. Consider, for example, the relationship between the entity beans we have created in this chapter (shown in Figure 7-21). Figure 7-21. Titan Cruises class diagram
If an EJB application invokes remove() on a CreditCard EJB, the Customer EJB that referenced that bean would now have a value of null for its creditCard relationship field, as the following code fragment illustrates: CustomerLocal customer = ... get Customer EJB CreditCardLocal creditCard = customer.getCreditCard(); creditCard.remove(); if(customer.getCreditCard() == null) // this will always be true The moment the remove() operation is invoked on the CreditCard EJB's local reference, the bean is disassociated from the Customer bean and deleted. The impact of removing a bean is even more interesting when that bean participates in several relationships. For example, invoking remove() on a Customer EJB will impact the relationship fields of the Reservation, Address, Phone, and CreditCard EJBs. With single EJB object relationship fields, such as the CreditCard EJB's reference to the Customer EJB, the field will be set to null for the entity bean that was removed. With collection-based relationship fields, the entity that was deleted will be removed from the collection. In some cases, you want the removal of an entity bean to cause a cascade of deletions. For example, if a Customer EJB is removed, we would want the Address EJBs referenced in its billingAddress and homeAddress relationship field to be deleted. This would avoid the problem of disconnected Address EJBs in the database. The <cascade-delete> element requests cascade delete; it can be used with one-to-one or one-to-many relationships. It does not make sense in many-to-many and many-to-one relationships, because of the nature of those relationships. For example, in the many-to-one relationship between the Reservation and Cruise EJBs, cancellation of a reservation by one passenger should not cancel the cruise itself! In other words, we would not want the deletion of a Reservation EJB to cause the deletion of its Cruise EJB. Here's how to modify the relationship declaration for the Customer and Address EJBs to obtain a cascade delete: <relationships> <ejb-relation> <ejb-relationship-role> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>homeAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <multiplicity>One</multiplicity> <cascade-delete/> <relationship-role-source> <ejb-name>AddressEJB</ejb-name> </relationship-role-source> </ejb-relationship-role> </ejb-relation> </relationships> If you do not specify a cascade delete, the ADDRESS record associated with the Address EJB will not be removed when the CUSTOMER record is deleted. This can result in a disconnected dependent object value, which means that the data is not linked to anything. In some cases, we want to specify a cascading delete to ensure that no detached entities remain after a bean is removed. In other cases, however, we do not want to use a cascading delete. If, for example, the ADDRESS record associated with an entity bean is shared by other CUSTOMER records (i.e., if two different customers reside at the same residence), we probably do not want it to be deleted when the CUSTOMER record is deleted. A cascade delete can be specified only on an entity bean that has a single reference to the entity being deleted. For example, you can specify a cascade delete in the <ejb-relationship-role> for the Phone EJB in the Customer-Phone relationship if the Customer is deleted, because each Phone EJB is referenced by only one Customer. However, you cannot specify a cascade delete for the Customer EJB in this relationship, because a Customer maybe referenced by many Phone EJBs. The entity bean that causes the cascade delete must have a multiplicity of One in the relationship. A cascade delete affects only the relationship for which it is specified. So, for example, if you specify a cascade delete for the tre the referential integrity of the database. For example, if the database is set up so that a foreign key must point to an existing record, deleting an entity's data could violate that restriction and cause a transaction rollback. Please refer to Workbook Exercise 7.3, Cascade Deletes in CMP 2.0. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.
| ||||||
|